﻿using JinYuan.Helper;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace PLC.PLCAlarm
{
    /// <summary>
    /// PLC报警记录模块
    /// </summary>
    public class PLCAlarmReader
    {
        /// <summary>
        /// 报警模板（从触摸屏导出的报警数据）
        /// </summary>
        private readonly List<AlarmFileData> alarmFileDataList;
        /// <summary>
        /// 报警记录路径（实际记录下来的实时报警）
        /// </summary>
        private readonly string alarmRecordPath;
        /// <summary>
        /// 是否忽略报警状态下的新增报警
        /// </summary>
        private readonly bool ignoreAlarm;
        /// <summary>
        /// 报警记录类型
        /// </summary>
        private readonly RecordType recordType;

        /// <summary>
        /// 初始化一个报警读取器
        /// </summary>
        /// <param name="AlarmFilePath">报警文件路径
        /// <para>
        /// 文件格式 .csv
        /// <para>字,位,触发数值,报警代码,报警内容</para>
        /// <para>40000,0,1,E001,急停报警(IO:A1_I0.05)</para>
        /// <para>40000,1,1,E002,上下料安全门报警(IO:A1_I0.06)</para>
        /// </para>
        /// </param>
        /// <param name="AlarmRecordPath">报警记录目录路径</param>
        /// <param name="recType">报警记录类型</param>
        /// <param name="ignore">是否忽略报警状态下的新增报警</param>
        public PLCAlarmReader(string AlarmFilePath, string AlarmRecordPath, RecordType recType = RecordType.RecordWhenStart, bool ignore = false)
        {
            alarmFileDataList = ReadAlarmFile(AlarmFilePath);
            alarmRecordPath = AlarmRecordPath;
            recordType = recType;
            ignoreAlarm = ignore;
            RecordAlarms = new List<RecordAlarmDataArgs>();
        }

        /// <summary>
        /// 新增报警事件
        /// </summary>
        public event EventHandler<RecordAlarmDataArgs> PLCAlarm_New;
        /// <summary>
        /// 报警复位事件
        /// </summary>
        public event EventHandler<RecordAlarmDataArgs> PLCAlarm_Remove;
        /// <summary>
        /// 当前记录中的报警
        /// </summary>
        public List<RecordAlarmDataArgs> RecordAlarms { get; }

        /// <summary>
        /// 读取本地报警文件
        /// </summary>
        /// <returns></returns>
        private List<AlarmFileData> ReadAlarmFile(string alarmFilePath)
        {
            List<AlarmFileData> fileAlarmList = new List<AlarmFileData>();
            try
            {
                if (!File.Exists(alarmFilePath))
                    return null;
                using (StreamReader sr = new StreamReader(alarmFilePath, Encoding.Default))
                {
                    if (!sr.EndOfStream)
                        sr.ReadLine();  // 读掉第一行标题行
                    while (!sr.EndOfStream)
                    {
                        string[] alarmData = sr.ReadLine().Split('\t');
                        fileAlarmList.Add(new AlarmFileData(alarmData));
                    }
                }
                return fileAlarmList;
            }
            catch (Exception ex)
            {
                LogHelper.Instance.WriteError("读取本地报警信息配置文件出错：" + ex.Message);
                return null;
            }
        }

        /// <summary>
        /// 寻找报警代码
        /// </summary>
        /// <param name="word">字地址</param>
        /// <param name="bit">位地址</param>
        /// <param name="value">值</param>
        /// <param name="alarmData">报警信息</param>
        /// <returns>是否找到</returns>
        private bool FindAlarm(int word, int bit, bool value, out AlarmFileData alarmData)
        {
            if (alarmFileDataList == null)
            {
                alarmData = null;
                return false;
            }
            foreach (var item in alarmFileDataList)
            {
                if (item.Word == word &&
                    item.Bit == bit &&
                    item.AlarmTriger == value)
                {
                    alarmData = item;
                    return true;
                }
            }
            alarmData = null;
            return false;
        }

        /// <summary>
        /// 获得当前报警列表
        /// </summary>
        /// <param name="startAddr">报警字开始地址</param>
        /// <param name="addrStep">报警地址递增步进（个别情况用）
        /// <para>比如按字读取基恩士MR地址时，字地址是40000，40100，40200这样增加的，此时就要把addrStep设成100</para>
        /// </param>
        /// <param name="length">报警字长度（有几个字）</param>
        /// <param name="readDatas">读到的数据</param>
        /// <param name="readPLCAlrms">返回的当前报警列表</param>
        /// <returns></returns>
        private bool GenerateAlarmList(int startAddr, int length, ushort[] readDatas, out List<AlarmFileData> readPLCAlrms, int addrStep = 1)
        {
            bool isAlarm = false;
            readPLCAlrms = new List<AlarmFileData>();
            for (int wi = 0; wi < length; wi++)
            {
                ushort word = readDatas[wi];
                if (word != 0)
                {
                    isAlarm = true;
                    bool[] bits = PLCDataTool.WordToBits(word);
                    for (int bi = 0; bi < 16; bi++)
                    {
                        if (FindAlarm(startAddr + wi * addrStep, bi, bits[bi], out AlarmFileData alarmdata))
                        {
                            if (!readPLCAlrms.Exists((p) => p.AlarmCode == alarmdata.AlarmCode))
                                readPLCAlrms.Add(alarmdata);
                        }
                    }
                }
            }
            return isAlarm;
        }

        /// <summary>
        /// 输入读取的数据，判断当前是否报警，保存报警记录
        /// </summary>
        /// <param name="startAddr">报警字开始地址</param>
        /// <param name="addrStep">报警地址递增步进（个别情况用）
        /// <para>比如按字读取基恩士MR地址时，字地址是40000，40100，40200这样增加的，此时就要把addrStep设成100</para>
        /// </param>
        /// <param name="length">报警字长度（有几个字）</param>
        /// <param name="readDatas">读到的数据</param>
        /// <returns></returns>
        public bool IsPLCAlarm(int startAddr, int length, ushort[] readDatas, int addrStep = 1)
        {
            if (GenerateAlarmList(startAddr, length, readDatas, out List<AlarmFileData> readPLCAlrms, addrStep))
            {
                // 新增
                if (!ignoreAlarm || RecordAlarms.Count <= 0)  //当不忽略报警状态下新增报警，或者当前是无报警状态的话，就记录
                {
                    foreach (var alrmItem in readPLCAlrms)
                    {
                        if (RecordAlarms.Exists(p => p.AlarmCode == alrmItem.AlarmCode))
                            continue;
                        // 记录新增报警
                        var recordData = new RecordAlarmDataArgs()
                        {
                            AlarmCode = alrmItem.AlarmCode,
                            AlarmInfo = alrmItem.AlarmInfo,
                            AlarmDate = DateTime.Now.ToString("yyyy/MM/dd"),
                            StartTime = DateTime.Now
                        };
                        RecordAlarms.Add(recordData);
                        SaveAlarmRecordWhenStart(recordData);
                        OnAlarm_New(recordData);
                    }
                }
                // 复位
                for (int i = RecordAlarms.Count - 1; i >= 0; i--)
                {
                    // 遍历记录中的报警，如果有当前报警中不包含的报警项，说明报警已复位
                    if (!readPLCAlrms.Exists(p => p.AlarmCode == RecordAlarms[i].AlarmCode))
                    {
                        SaveAlarmRecordWhenFinish(RecordAlarms[i], DateTime.Now);
                        OnAlarm_Remove(RecordAlarms[i]);
                        RecordAlarms.RemoveAt(i);
                    }
                }
                return true;
            }
            else
            {
                // 全都复位
                for (int i = RecordAlarms.Count - 1; i >= 0; i--)
                {
                    SaveAlarmRecordWhenFinish(RecordAlarms[i], DateTime.Now);
                    OnAlarm_Remove(RecordAlarms[i]);
                    RecordAlarms.RemoveAt(i);
                }
                return false;
            }
        }

        /// <summary>
        /// 触发新增报警事件
        /// </summary>
        /// <param name="args"></param>
        private void OnAlarm_New(RecordAlarmDataArgs args)
        {
            // 以线程安全的方式引发事件
            var temp = PLCAlarm_New;
            temp?.Invoke(this, args);
        }
        /// <summary>
        /// 触发报警复位事件
        /// </summary>
        /// <param name="args"></param>
        private void OnAlarm_Remove(RecordAlarmDataArgs args)
        {
            // 以线程安全的方式引发事件
            var temp = PLCAlarm_Remove;
            temp?.Invoke(this, args);
        }


        #region 保存报警记录
        /// <summary>
        /// 当报警消除的时候记录已消除的报警
        /// </summary>
        /// <param name="alrmData">报警数据</param>
        /// <param name="endTime">结束时间</param>
        private void SaveAlarmRecordWhenFinish(RecordAlarmDataArgs alrmData, DateTime endTime)
        {
            if (recordType != RecordType.RecordWhenFinish)
                return;

            string start = alrmData.StartTime.ToString("HH:mm:ss");
            string end = endTime.ToString("HH:mm:ss");
            string totaltime = (endTime - alrmData.StartTime).TotalMinutes.ToString("f2");

            if (!Directory.Exists(alarmRecordPath))
            {
                Directory.CreateDirectory(alarmRecordPath);
            }
            try
            {
                string filePath = Path.Combine(alarmRecordPath, DateTime.Now.ToString("yyyy-MM-dd") + ".csv");
                if (!File.Exists(filePath))
                {
                    using (StreamWriter sw = new StreamWriter(filePath))
                    {
                        sw.WriteLine("报警信息,报警代码,统计日期,开始时间,结束时间,消耗时间(min)");
                        sw.WriteLine(alrmData.AlarmInfo + "," + alrmData.AlarmCode + "," + alrmData.AlarmDate + "," + start + "," + end + "," + totaltime);
                    }
                }
                else
                {
                    using (StreamWriter sw = new StreamWriter(filePath, true))
                    {
                        sw.WriteLine(alrmData.AlarmInfo + "," + alrmData.AlarmCode + "," + alrmData.AlarmDate + "," + start + "," + end + "," + totaltime);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Instance.WriteError(ex.Message);
            }

        }

        /// <summary>
        /// 当报警触发的时候记录报警
        /// </summary>
        /// <param name="alrmData">报警数据</param>
        private void SaveAlarmRecordWhenStart(RecordAlarmDataArgs alrmData)
        {
            if (recordType != RecordType.RecordWhenStart)
                return;

            string start = alrmData.StartTime.ToString("HH:mm:ss");

            if (!Directory.Exists(alarmRecordPath))
            {
                Directory.CreateDirectory(alarmRecordPath);
            }
            try
            {
                string filePath = Path.Combine(alarmRecordPath, DateTime.Now.ToString("yyyy-MM-dd") + ".csv");
                if (!File.Exists(filePath))
                {
                    using (StreamWriter sw = new StreamWriter(filePath))
                    {
                        sw.WriteLine("报警信息,报警代码,统计日期,开始时间");
                        sw.WriteLine(alrmData.AlarmInfo + "," + alrmData.AlarmCode + "," + alrmData.AlarmDate + "," + start);
                    }
                }
                else
                {
                    using (StreamWriter sw = new StreamWriter(filePath, true))
                    {
                        sw.WriteLine(alrmData.AlarmInfo + "," + alrmData.AlarmCode + "," + alrmData.AlarmDate + "," + start);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Instance.WriteError(ex.Message);
            }

        }

        #endregion
    }
}
